{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 1. 介绍\n",
    "\n",
    "下图是计算机视觉的三个阶段，其中前两个阶段都属于数字图像处理的范畴。有的同学为了追求学习速度直接跳过数字图像处理部分，这是不可取的。数字图像处理不仅是cv深度学习的基础，也是面试题的高频考点。\n",
    "\n",
    "<div align=center><img src=\"images/3level.png\" width=\"70%\" ></div>\n",
    "<div align=center>计算机视觉的三个阶段</div>\n",
    "\n",
    "# 1.1 环境配置"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {},
   "source": [
    "python -m pip install opencv-python==4.5.2.52"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.2 什么是数字图像？\n",
    "\n",
    "我们可以用一个二维函数$f(x,y)$来定义一张图像，其中$(x,y)$是空间坐标，任意一对空间坐标$(x,y)$处的幅值$f$称为图像在该点的强度或灰度。特别地，当$x,y$和灰度值$f$都是有限的离散值时，我们称该图像为**数字图像**。\n",
    "\n",
    "**注意：大多数图像都定义为一个矩形。**\n",
    "\n",
    "下面，我们从定义域和值域来研究这个函数。\n",
    "\n",
    "- 定义域\n",
    "\n",
    "对于数字图像来说，$x$和$y$是有限的离散值，即$x=0, 1, 2, \\dots, M-1$ 和 $y = 0, 1, 2, \\dots, N-1$，其中$M$代表图像的高度（垂直方向的像素数），$N$代表图像的宽度（水平方向的像素数）。同一张图像，如果$M \\times N$大，图像的像素点越多；反之，像素点越少。下图是同一张图不同$M$和$N$下的结果：\n",
    "\n",
    "<center><img src=\"images/doge.png\" ></center>\n",
    "\n",
    "- 值域\n",
    "\n",
    "对于数字图像来说，$f(x ,y)$的值域通常为$\\left\\{0, 1, 2, \\dots, 255\\right\\}$，即我们用8 bits(1字节)来存储$(x, y)$这一点的灰度值。用8 bits存储的图像拥有256个**灰度级**（颜色），同样的，用 7 bits 存储的图像拥有128个灰度级。同一图片不同灰度级的结果如下所示：\n",
    "\n",
    "<table>\n",
    "    <tr>\n",
    "        <td ><center><img src=\"images/doge256.jpg\" >(a) 8 bits（256灰度级） </center></td>\n",
    "        <td ><center><img src=\"images/doge128.jpg\" >(b) 7 bits（128灰度级） </center></td>\n",
    "        <td ><center><img src=\"images/doge64.jpg\" >(c) 6 bits（64灰度级） </center></td>\n",
    "        <td ><center><img src=\"images/doge32.jpg\" >(d) 5 bits（32灰度级） </center></td>\n",
    "    </tr>\n",
    "    <tr>\n",
    "        <td ><center><img src=\"images/doge16.jpg\" >(e) 4 bits（16灰度级） </center></td>\n",
    "        <td ><center><img src=\"images/doge8.jpg\" >(f) 3 bits（8灰度级） </center></td>\n",
    "        <td ><center><img src=\"images/doge4.jpg\" >(g) 2 bits（4灰度级） </center></td>\n",
    "        <td ><center><img src=\"images/doge2.jpg\" >(h) 1 bit（2灰度级） </center></td>\n",
    "    </tr>\n",
    "</table>\n",
    "\n",
    "在灰度级不断减少的过程中，图像会出现**伪轮廓**，如16灰度级中天空部分，这是因为平滑区域中的灰度级数不足引起的，在以16级灰度或更少级灰度显示的图像中，伪轮廓通常十分明显。\n",
    "\n",
    "<center><img src=\"images/levelmap.png\" width=\"50%\" ></center>\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.3 比特平面\n",
    "\n",
    "由1.2知道，数字图像中每一个像素点的灰度值通常用8bits来存储，所以一共有256个灰度级。需要注意的是，我们一般用无符号八位整型来存储灰度值，在numpy中的数据类型为uint8，无符号数的最高位不代表符号，一个n位的无符号数的表示范围为$0 \\sim 2^{n} - 1$，为了更好地理解无符号数，我们需要知道什么是有符号数，这里介绍两种有符号数的表示方式：\n",
    "\n",
    "a) 原码\n",
    "\n",
    "在机器中，数的正负号是无法识别的，我们一般规定“0”代表正数，“1”代表负数，并约定二进制数的最高位为符号数，即将符号位放在有效数字的前面组成有符号数。原码就是一种有符号数的表示方式，原码的最高位为符号位，n位的原码表示范围为$-2^{n-1} + 1 \\sim 2^{n-1} - 1$，例如8位的原码表示范围为$-127 \\sim 127$，0110 0011表示99，1110 0011表示-99。\n",
    "\n",
    "b) 补码\n",
    "\n",
    "计算机中有符号数大多都是以补码的形式存储，因为原码不好做减法，而补码做减法却很方便。一个n位的补码表示范围为$-2^{n-1} \\sim 2^{n-1} - 1$，补码的最高位同样为符号位，正的补码与原码相同，对于负数来说，我们先用原码表示除这个负数，然后对符号位外的每位取反，最后加1就可以得到该数的补码。例如补码的0110 0011同样表示99，要表示-99我们需要先写出-99的原码，即1110 0011，除符号位之外每位取反：1001 1100，最后加1: 1001 1101就是-99的补码。\n",
    "\n",
    "言归正传，比特平面i，就是将图像的第i位提取出来单独组成一张图片，由于生成的图片的灰度值只用1bit来存储，所以该图像的灰度级为2，也就是说生成的图像是一张二值图，每个像素点的灰度只能取0（黑）或255（白）。下面我们来看一个例子，原图是一个$2 \\times 2$的图像，它由四个像素点组成：\n",
    "\n",
    "$$ \\left( \\begin{matrix} 99 & 24 \\\\ 234 & 180 \\end{matrix} \\right)$$\n",
    "\n",
    "将灰度值转成二进制表示：\n",
    "\n",
    "$$ \\left( \\begin{matrix} 0110 0011 & 0001 1000 \\\\ 1110 1010 & 1011 0100 \\end{matrix} \\right)$$\n",
    "\n",
    "比特平面8为：\n",
    "\n",
    "$$ \\left( \\begin{matrix} 0 & 0 \\\\ 255 & 255 \\end{matrix} \\right)$$\n",
    "\n",
    "比特平面3为：\n",
    "\n",
    "$$ \\left( \\begin{matrix} 0 & 0 \\\\ 0 & 255 \\end{matrix} \\right)$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.4 颜色组成\n",
    "\n",
    "- 彩色图\n",
    "\n",
    "我们通常用R（红）、G（绿）、B（蓝）三个通道来表示一张彩色图像，也就是说，一张彩色图像由三个函数$R(x, y)$、$G(x, y)$和$B(x, y)$组成。\n",
    "\n",
    "<table>\n",
    "    <tr>\n",
    "        <td ><center><img src=\"images/doge_blue.jpg\"  >蓝 </center></td>\n",
    "        <td ><center><img src=\"images/doge_green.jpg\" >绿 </center></td>\n",
    "        <td ><center><img src=\"images/doge_red.jpg\" >红 </center></td>\n",
    "    </tr>\n",
    "</table>\n",
    "\n",
    "如果每个通道的灰度值用8bits来存储，那一张彩色图像总共可以表示$2^{8+8+8}=16777216 $种颜色，这就是经常所说的“24位真彩色” 。\n",
    "<div align=center><img src=\"images/rgb.jpg\" width=\"30%\" ></div>\n",
    "<div align=center>RGB色彩空间</div>\n",
    "\n",
    "- 灰度图\n",
    "\n",
    "彩色图像是由R、G、B三通道组成的图片，灰度图（如图(a)）就是单通道图片。彩色图像可以通过著名的心理学公式转换为灰度图：\n",
    "\n",
    "$$Gray = 0.299\\times R + 0.587 \\times G + 0.114 \\times B$$"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.5 数字图像的矩阵表示\n",
    "\n",
    "图像的函数表示太过抽象，一种更直观的表示方法是矩阵表示。$f(0, 0)$代表$x=0, y=0$这一点的灰度值，这一元素位于矩阵的左上角，$x = M-1, Y=N-1$这点的像素值位于矩阵右下角，说明图像坐标轴的原点位于左上角，x轴位于垂直方向且从上往下由零开始不断递增，y轴位于水平方向且从左往右由零开始不断递增。从另一个角度说，矩阵$M$为矩阵的行数，$N$为矩阵的列数，所以$M$对应图像的高，$N$对应图像的宽。\n",
    "\n",
    "<div align=center></div>\n",
    "\n",
    "<table>\n",
    "    <tr>\n",
    "        <td ><center><img src=\"images/matrix.png\" width=\"60%\" > </center></td>\n",
    "        <td ><center><img src=\"images/axis.png\"> </center></td>\n",
    "    </tr>\n",
    "</table>\n",
    "\n",
    "下面是一张图片和它对应的矩阵：\n",
    "\n",
    "<table>\n",
    "    <tr>\n",
    "        <td ><center><img src=\"images/eight1.png\" width=\"50%\"> </center></td>\n",
    "        <td ><center><img src=\"images/eight2.png\" width=\"50%\"> </center></td>\n",
    "    </tr>\n",
    "</table>\n",
    "\n",
    "**注意：灰度图片对应一个矩阵，彩色图片对应三个矩阵**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.6 Python OpenCV基本操作\n",
    "\n",
    "OpenCV帮助文档：https://docs.opencv.org/4.5.2/ ，以读取图片(cv2.imread)为例  \n",
    "\n",
    "<div align=center><img src=\"images/opencvdoc.png\" width=\"100%\" ></div>\n",
    "\n",
    "1. 读取图片"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(446, 1678, 3)"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "\n",
    "img = cv2.imread('images/doge.png')  # 默认是彩色图片，图片路径中不能包含中文，读取的数据结构是numpy数组\n",
    "img.shape  # 数组的形状：446是三维数组的第一个维度，对应图片的高；1678是三维数组的第二个维度，对应图片的宽；3对应图片的通道数，这里是彩色图片所以为3。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'4.5.5'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cv2.__version__"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "array([  2,   3,   4,   5,   6,   7,   8,   9,  10,  11,  12,  13,  14,\n",
       "        15,  16,  17,  18,  19,  20,  21,  22,  23,  24,  25,  26,  27,\n",
       "        28,  29,  30,  31,  32,  33,  34,  35,  36,  37,  38,  39,  40,\n",
       "        41,  42,  43,  44,  45,  46,  47,  48,  49,  50,  51,  52,  53,\n",
       "        54,  55,  56,  57,  58,  59,  60,  61,  62,  63,  64,  65,  66,\n",
       "        67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,  79,\n",
       "        80,  81,  82,  83,  84,  85,  86,  87,  88,  89,  90,  91,  92,\n",
       "        93,  94,  95,  96,  97,  98,  99, 100, 101, 102, 103, 104, 105,\n",
       "       106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118,\n",
       "       119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131,\n",
       "       132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144,\n",
       "       145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157,\n",
       "       158, 159, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170,\n",
       "       171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183,\n",
       "       184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196,\n",
       "       197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,\n",
       "       210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222,\n",
       "       223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235,\n",
       "       236, 237, 238, 239, 240, 241, 242, 243, 244, 245, 246, 247, 248,\n",
       "       249, 250, 251, 252, 253, 254, 255], dtype=uint8)"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "np.unique(img)  # 该图像包含的灰度值，可以看出灰度值的范围在0~255之间"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(446, 1678)"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "gray = cv2.imread('images/doge.png', 0)  # 第二个参数为0读取为灰度图片\n",
    "gray.shape"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 显示图片"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "cv2.imshow('origin', img)  # 显示彩色图，第一个参数是窗口名，第二个参数是图像所对应的numpy数组\n",
    "cv2.imshow('gray', gray)  # 显示灰度图\n",
    "cv2.waitKey(0)  # 参数为0或负数为按任意键继续，正数则与time.sleep功能相同\n",
    "cv2.destroyAllWindows()  # 关闭所有窗口"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 写入图片"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "cv2.imwrite('images/gray.jpg', gray)  # 第一个参数为文件名，第二个参数为图片对应的numpy数组"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. RGB颜色对照表\n",
    "\n",
    "对于灰度图来说，0对应黑色，255对应白色，灰度值越大对应颜色越白（亮），越小颜色越黑（暗）。\n",
    "\n",
    "对于彩色图来说，常用的RGB颜色：\n",
    "\n",
    "|颜色|R|G|B\n",
    "|-|-|-|-\n",
    "|白色|255|255|255\n",
    "|黑色|0|0|0\n",
    "|红色|255|0|0\n",
    "|橙色|255|165|0\n",
    "|黄色|255|255|0\n",
    "|绿色|0|255|0\n",
    "|青色|0|255|255\n",
    "|蓝色|0|0|255\n",
    "|紫色|160|32|240\n",
    "\n",
    "\n",
    "因为历史原因，OpenCV读取和显示的图片通道顺序为B、G、R，而其他包如matplotlib和Pillow通道顺序为RGB，所以如果我们想用OpenCV读取图片再用matplotlib或Pillow显示的话，需要将BGR转化为RGB。将BGR转为RGB有下面两种方式：\n",
    "\n",
    "- cv2.cvtColor"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n",
    "cv2.imshow('origin', rgb)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- numpy数组特性"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 方法一：\n",
    "rgb1 = img[:, :, ::-1]  # ::-1代表倒序，BGR倒序后就为RGB\n",
    "# 方法二：\n",
    "rgb2 = img[:, :, [2, 1, 0]]  # 利用索引将B和R调换位置\n",
    "cv2.imshow('rgb1', rgb1)\n",
    "cv2.imshow('rgb2', rgb2)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "对于RGB转BGR同理即可。下面演示如何根据RGB颜色表生成彩色图片："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "img = np.zeros((500, 250, 3), dtype=np.uint8)  # 生成一张高为500宽为250的黑色图像，注意数据类型必须为无符号八位整型\n",
    "cv2.imshow('black', img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成蓝色图片\n",
    "img[:, :, 0] = 255\n",
    "cv2.imshow('black', img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 生成绿色图片\n",
    "img = np.zeros((500, 250, 3), dtype=np.uint8)\n",
    "img[:, :, 1] = 255\n",
    "cv2.imshow('green', img)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "5. 截图\n",
    "\n",
    "由上面的例子我们知道，doge.png其实是由四张图片拼接而成，如果现在需要把其中的第二张图片截取出来，可以做下面的操作："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "img = cv2.imread('images/doge.png')\n",
    "h, w, c = img.shape\n",
    "second_fig = img[:, int(w/4):2*int(w/4), :]\n",
    "cv2.imshow('second_fig', second_fig)\n",
    "cv2.waitKey(0)\n",
    "cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "上面提到的方式是比较粗略的截图方式，如果需要精准截图，windows用户可以利用画图工具确定截图坐标，linux可以直接imshow来确定坐标。\n",
    "\n",
    "<div align=center><img src=\"images/crop.png\" width=\"60%\" ></div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.7 本节相关代码\n",
    "\n",
    "- 1.2 定义域\n",
    "\n",
    "**注意：由于$25\\times 25$的图像显示出来太小，为了方便大家观看，我先将$1080\\times 1080$的图片缩小到$25 \\times 25$，再用最近邻插值（第三节中会详细讨论）放大到$1080 \\times 1080$，但实际高和宽并非25，而是1080，对于$50 \\times 50$和$100 \\times 100$也是如此。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(1080, 1080, 3)\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "import cv2 \n",
    "import os \n",
    "\n",
    "img = cv2.imread('images/doge1080.jpg')\n",
    "print(img.shape)\n",
    "dst = cv2.resize(img, [25, 25])\n",
    "dst = cv2.resize(dst, [1080, 1080], interpolation = cv2.INTER_NEAREST)\n",
    "cv2.imshow('test', dst)\n",
    "key = cv2.waitKey()\n",
    "if key == 27:\n",
    "    cv2.destroyAllWindows()\n",
    "cv2.imwrite('images/doge25.jpg', dst)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 1.2 值域\n",
    "\n",
    "**注意：由于OpenCV只支持显示8 bits的图片，这里降低灰度级并没有减少存储空间，即灰度级仍是256。具体操作如下：**\n",
    "\n",
    "如果灰度级为2，则原图像灰度值为0\\~127的点灰度值修改为0，原图像128\\~255修改为128。\n",
    "\n",
    "如果灰度级为4，则原图像灰度值为0\\~63的点灰度值修改为0，原图像64\\~127修改为64，原图像128\\~191修改为128，原图像192\\~255修改为192。\n",
    "\n",
    "其他灰度级以此类推。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "import numpy as np\n",
    "def ReduceDepth(img, depth):\n",
    "    assert depth <= 256\n",
    "    ratio = 256 / depth\n",
    "    dst = np.uint8(img/ratio)*ratio\n",
    "    dst = dst.astype(np.uint8)\n",
    "    cv2.imshow('ori', dst)\n",
    "    key = cv2.waitKey(0)\n",
    "    cv2.destroyAllWindows()\n",
    "    cv2.imwrite(f'./images/doge{depth}.jpg', dst)\n",
    "for i in range(1, 9):\n",
    "    img = cv2.imread('./images/doge1080.jpg', 0)\n",
    "    ReduceDepth(img, 2**i)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 1.3"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cv2\n",
    "\n",
    "img = cv2.imread('images/doge1080.jpg')\n",
    "b = np.zeros_like(img)\n",
    "g = np.zeros_like(img)\n",
    "r = np.zeros_like(img)\n",
    "b[:, :, 0] = img[:, :, 0]\n",
    "g[:, :, 1] = img[:, :, 1]\n",
    "r[:, :, 2] = img[:, :, 2]\n",
    "cv2.imshow('ori', img)\n",
    "cv2.imshow('r', r)\n",
    "cv2.imshow('g', g)\n",
    "cv2.imshow('blue', b)\n",
    "cv2.imwrite('images/doge_blue.jpg', b)\n",
    "cv2.imwrite('images/doge_green.jpg', g)\n",
    "cv2.imwrite('images/doge_red.jpg', r)\n",
    "\n",
    "key = cv2.waitKey()\n",
    "if key == 27:\n",
    "    cv2.destroyAllWindows()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 1.8 课后作业\n",
    "\n",
    "实现images文件夹下doge1080.jpg图片的比特平面1~8，将图片保存下来，并分析哪个平面包含了最多的噪声。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "参考文献\n",
    "\n",
    "[1] Stanford CS232\n",
    "\n",
    "[2] ShanghaiTech CS270\n",
    "\n",
    "[3] 冈萨雷斯. 数字图像处理（第四版）."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python(pytorch-cuda111)",
   "language": "python",
   "name": "pytorch-cuda111"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
